package transcoding

import java.io.IOException
import com.xuggle.xuggler.IContainer
import java.net.URLConnection
import com.xuggle.xuggler.IStream
import com.xuggle.xuggler.ICodec
import com.xuggle.xuggler.IStreamCoder
import play.api.Logger
import java.io.ByteArrayOutputStream

abstract class MediaType
case object Video extends MediaType
case object Audio extends MediaType
case object Image extends MediaType
case object Other extends MediaType

//object MediaType extends Enumeration {
//     type MediaType = Value
//     val Video, Audio, Image, Other = Value
//}

class MediaFile(val filePath: String, container: Option[IContainer]) {

  def this(filePath: String) = this(filePath, None)
  def this(container: IContainer) = this(container.getURL(), Some(container))

  private lazy val videoStream: Option[IStream] = {
    allStreams.map(seq => seq.find(s => s.getStreamCoder().getCodec().getType().equals(ICodec.Type.CODEC_TYPE_VIDEO)))
      .getOrElse(None)
  }
  private lazy val audioStream: Option[IStream] = {
    allStreams.map(seq => seq.find(s => s.getStreamCoder().getCodec().getType().equals(ICodec.Type.CODEC_TYPE_AUDIO)))
      .getOrElse(None)
  }
  
  private val allStreams = { container.map(cont => (0 to cont.getNumStreams() - 1).map(i => cont.getStream(i))) }
  
  val format = container.map(cont => cont.getContainerFormat())
  
  val metaData = container.map(cont => cont.getMetaData )

  /*IContainer container = IContainer.make();
 * if (container.open("myfile.flv", IContainer.Type.WRITE, null) <0)
 *
 * &nbsp;&nbsp;throw new RuntimeException("failed to open");
 * IStream stream = container.addNewStream(0);
 * IStreamCoder coder = stream.getStreamCoder();
 * coder.setCodec(ICodec.ID.CODEC_ID_MP3);
 * coder.setSampleRate(22050);
 * coder.setChannels(2);
 * coder.setBitRate(64000);
 * if (coder.open()<0) throw new RuntimeException("could not open coder");
 *
 * if (container.writeHeader() < 0) throw new RuntimeException();
 * IPacket packet = IPacket.make();
 * while( ... have more data to process ... ) {
 *
 * &nbsp;&nbsp;then assuming it generated an IPacket for you...
 * &nbsp;&nbsp;if (container.writePacket(packet)<0)
 * &nbsp;&nbsp;&nbsp;&nbsp;throw new RuntimeException("could not write
 * packet");
 * }
 * if (container.writeTrailer() <0) throw new RuntimeException();
 * container.close();
 */
  /*def headers = {

    def getHeaders(container: IContainer) = {
      
      Logger.debug(container.getMetaData.toString)
      
      val outContainer = IContainer.make
      val outStream = new ByteArrayOutputStream
//      if (outContainer.open(outStream, inputFormat.get) < 0)
//        throw new RuntimeException("failed to open to write headers")
      if (outContainer.open(filePath+"_header.flv", IContainer.Type.WRITE, null) < 0)
        throw new RuntimeException("failed to open to write headers")
      outContainer.setFormat(inputFormat.get)
      val stream = outContainer.addNewStream(0)
      val streamCoder = IStreamCoder.make(IStreamCoder.Direction.ENCODING)
      streamCoder.setCodec(this.audioStream.get.getStreamCoder.getCodec)
      stream.setStreamCoder(streamCoder)
      
      streamCoder.setSampleRate(22050)
      streamCoder.setChannels(2)
      streamCoder.setBitRate(64000)
      if (streamCoder.open()<0) throw new RuntimeException("could not open coder")
      if (outContainer.writeHeader() < 0) throw new RuntimeException("could not write headers");
      if (outContainer.writeTrailer() <0) throw new RuntimeException("could not write trailer");
      
      outContainer.close();
      Logger.debug("WROTE "+filePath+"_header.flv")
      
      ""
    }

    container.map(cont => getHeaders(cont))
  }*/

  val bitRate = {
    container.map(cont => cont.getBitRate)
  }

  val duration = {
    container.map(cont => cont.getDuration() / 1000000)
  }

  val videoDimensions = {
    videoStream match {
      case None => (0, 0)
      case Some(coder) =>
        val sc = coder.getStreamCoder()
        (sc.getWidth(), sc.getHeight())
    }
  }

  lazy val mimeType = {
    val defMIME: String = MediaFile.getMIMEType(filePath)
    if (defMIME == null) {
      val typeMIME = format.map(inputf => inputf.getInputFormatShortName().split(",").headOption)
        .getOrElse(Some("unknown")).get
      val typeFormat: String = mediaType match {
        case Video => "video"
        case Audio => "audio"
        case Image => "image"
        case Other => "other"
      }

      "%s/%s".format(MediaFile.NON_MEDIA_TYPE.getOrElse(typeMIME, typeFormat), MediaFile.MIME_TYPES.getOrElse(typeMIME, typeMIME))
    } else defMIME
  }

  lazy val mediaType: MediaType = {
    if (isVideo) Video
    else if (isAudio) Audio
    else if (isImage) Image
    else Other
  }

  lazy val isVideo = {
    def hasTXTMime = {
      val mime = MediaFile.getMIMEType(filePath)
      mime != null && mime.startsWith("text")//<- Xuggler bug
    }

    !videoStream.isEmpty && !hasTXTMime
  }

  lazy val isAudio = {
    !isVideo &&
      !audioStream.isEmpty
  }

  lazy val isImage = {
    def hasImageMIME = {
      val mime = MediaFile.getMIMEType(filePath)
      mime != null && mime.startsWith("image")
    }

    !isVideo && !isAudio && hasImageMIME
  }

  override def equals(other: Any): Boolean = {
    if (other.isInstanceOf[MediaFile]) {
      this.filePath == other.asInstanceOf[MediaFile].filePath
    } else
      false
  }

  override def hashCode = filePath.hashCode
}

object MediaFile {

  private val MIME_TYPES = Map("mp3" -> "mpeg",
    "oga" -> "ogg",
    "ogx" -> "ogg",
    "aac" -> "mp4",
    "quicktime" -> "mpeg",
    "mov" -> "mpeg",
    "mkv" -> "x-matroska")
  private val NON_MEDIA_TYPE = Map("ogx" -> "application", "gif" -> "image")
  //            ,
  //            {"ogg", "audio/ogg"},
  //            {"oga", "audio/ogg"},
  //            {"ogx", "application/ogg"},
  //            {"aac", "audio/mp4"},
  //            {"m4a", "audio/mp4"},
  //            {"flac", "audio/flac"},
  //            {"wav", "audio/x-wav"},
  //            {"wma", "audio/x-ms-wma"},
  //            {"ape", "audio/x-monkeys-audio"},
  //            {"mpc", "audio/x-musepack"},
  //            {"shn", "audio/x-shn"},
  //
  //            {"flv", "video/x-flv"},
  //            {"avi", "video/avi"},
  //            {"mpg", "video/mpeg"},
  //            {"mpeg", "video/mpeg"},
  //            {"mp4", "video/mp4"},
  //            {"m4v", "video/x-m4v"},
  //            {"mkv", "video/x-matroska"},
  //            {"mov", "video/quicktime"},
  //            {"wmv", "video/x-ms-wmv"},
  //            {"ogv", "video/ogg"},
  //            {"divx", "video/divx"},
  //            {"m2ts", "video/MP2T"},
  //
  //            {"gif", "image/gif"},
  //            {"jpg", "image/jpeg"},
  //            {"jpeg", "image/jpeg"},
  //            {"png", "image/png"},
  //            {"bmp", "image/bmp"},
  //    );

  val MIME_MAPS = URLConnection.getFileNameMap()

  def orElse[A](equalThen: (A, A))(actualVal: A): A = {
    if (actualVal.equals(equalThen._1))
      equalThen._2
    else actualVal
  }

  def apply(filePath: String): MediaFile = {
    val file = new java.io.File(filePath)
    if (!file.exists()) {
      throw new IOException("File '%s' does not exists".format(file.getAbsolutePath))
    }
    apply(file)
  }

  def apply(file: java.io.File): MediaFile = {
    val container = IContainer.make()
    val absolutePath = file.getAbsolutePath
    try {

      val suffix = getFileSuffix(file.getName).getOrElse("")
      //FIXME: getNumStreasm crashes the JVM for some non-media type
      if (!List("flv", "mov", "avi", "mkv", "mpg", "mpg4", "wav", "wma", "ogg", "oga", "mp4").contains(suffix))
        throw new IOException("Not supported Media File %s".format(file.getAbsolutePath))

      if (container.open(file.getAbsolutePath, IContainer.Type.READ, null) < 0)
        throw new IOException("Could not open file '%s' for reading. Is it an audio/video file?".format(file.getAbsolutePath))

      new MediaFile(container)
    } catch {
      case error: IOException =>
        Logger.trace("Received error while creating MediaFile, returning simple media file (%s)".format(error.getMessage))
        new MediaFile(file.getAbsolutePath)
    } finally {
      container.close
      container.delete
    }
  }

  val transcodingCmds = Map("video" -> "/home/giacomo/Code/ffmpeg -ss $offset -i $file -async 1 -b $bitrate -s $size -ar 44100 -ac 2 -v 0 -f flv -vcodec libx264 -preset superfast -threads 0 -")

  def getMIMEType(filePath: String) = {
    MIME_MAPS.getContentTypeFor(filePath)
  }

  def getFileSuffix(fileName: String): Option[String] = {
    val nameSeg = fileName.split('.')
    if (nameSeg.isEmpty) None
    else Some(nameSeg(nameSeg.length - 1))
  }
}